// +------------------------------------------------------------------------+
// | XRes - Mac OS X Resource Monitor                                       |
// +------------------------------------------------------------------------+
// | Copyright (c) 2007 Zack Bartel                                         |
// +------------------------------------------------------------------------+
// | This program is free software; you can redistribute it and/or          |
// | modify it under the terms of the GNU General Public License            | 
// | as published by the Free Software Foundation; either version 2         | 
// | of the License, or (at your option) any later version.                 |
// |                                                                        |
// | This program is distributed in the hope that it will be useful,        |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of         |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          |
// | GNU General Public License for more details.                           |
// |                                                                        |
// | You should have received a copy of the GNU General Public License      |
// | along with this program; if not, write to the Free Software            |
// | Foundation, Inc., 59 Temple Place - Suite 330,                         |
// | Boston, MA  02111-1307, USA.                                           |
// +------------------------------------------------------------------------+
// | Author: Zack Bartel <zack@bartel.com>                                  |
// +------------------------------------------------------------------------+ 
//
//  XRProcess.m
//  XRes
//
//  Created by Zack Bartel on 3/7/08.
//  Copyright 2008 Zack Bartel. All rights reserved.
//
//  TODO: http://developer.apple.com/qa/qa2001/qa1123.html

#import "XRProcess.h"


@interface XRProcess (Private)

+ (NSString *)readTaskDataWithArguments: (NSArray *)arguments;
+ (XRProcess *)parseLine: (NSString *)line;

@end


@implementation XRProcess

+ (NSArray *)allProcesses
{
    return [XRProcess allProcessesWithAttributes: XRProcessAllProcesses orderedBy: XRProcessOrderByPercentCPUUsage];
}

+ (NSArray *)allWindowedProcesses
{
    return [XRProcess allProcessesWithAttributes: XRProcessWindowedProcessesOnly orderedBy: XRProcessOrderByPercentCPUUsage];
}

+ (NSArray *)allProcessesOrderedBy:(XRProcessOrderBy)orderBy
{
    return [XRProcess allProcessesWithAttributes: XRProcessAllProcesses orderedBy: orderBy];
}

+ (NSArray *)allProcessesWithAttributes:(XRProcessAttribute) attributes orderedBy: (XRProcessOrderBy)order
{
    NSString *data;
    XRProcess *process;
    NSArray * result;
    NSMutableArray *processes = [NSMutableArray array];
    
    data = [XRProcess readTaskDataWithArguments: [NSArray arrayWithObjects: @"aux", nil]];
    
    NSMutableArray *lines = [NSMutableArray arrayWithArray: 
                                    [data componentsSeparatedByCharactersInSet:
                                                [NSCharacterSet newlineCharacterSet]]];
                                                
    //Remove Header and blank last line
    [lines removeObjectAtIndex: 0];
    [lines removeLastObject];
    
    //TODO: Add User Only
    for (NSString *line in lines)
    {
        process = [XRProcess parseLine: line];
        if (attributes == XRProcessWindowedProcessesOnly)
        {
            if ([process isWindowed])
                [processes addObject: process];
        }
        else if (attributes == XRProcessUserProcessesOnly)
        {
            if ([[process user] isEqualToString: [NSString stringWithCString: getenv("USER")]])
                [processes addObject: process];
        }
        else
        {
            [processes addObject: process];
         }
               
        [process release];
    }
        
    [data release];

    //Handle ordering
    switch (order)
    {
        case XRProcessOrderByCommandName:
            result = [processes sortedArrayUsingSelector: @selector(commandNameCompare:)];
            break;
            
        case XRProcessOrderByResidentSize:
            result = [processes sortedArrayUsingSelector: @selector(residentMemoryCompare:)];
            break;
            
        case XRProcessOrderByVirtualMemorySize:
            result = [processes sortedArrayUsingSelector: @selector(vitualMemorySizeCompare:)];
            break;
            
        case XRProcessOrderByPercentCPUUsage:
            result = [processes sortedArrayUsingSelector: @selector(percentCPUUsageCompare:)];
            break;
            
        case XRProcessOrderByUser:
            result = [processes sortedArrayUsingSelector: @selector(userCompare:)];
            break;
            
        default:
            result = [processes sortedArrayUsingSelector: @selector(commandNameCompare:)];
            break;
    }
    
    return result;
}

+ (XRProcess *)processWithPid:(int)thePid
{
    XRProcess *process = nil;
    
    NSString *data = [XRProcess readTaskDataWithArguments: [NSArray arrayWithObjects: @"aux", [NSNumber numberWithInt: thePid]]];
    
    NSMutableArray *lines = [NSMutableArray arrayWithArray: 
                                    [data componentsSeparatedByCharactersInSet:
                                                [NSCharacterSet newlineCharacterSet]]];
                   
    if ([lines count] > 1)
    {
        process = [XRProcess parseLine: [lines objectAtIndex: 1]];
    }
    
    [data release];
    
    return process;
}

- (NSString *) user {
  return user;
}

- (void) setUser: (NSString *) newValue {
  [user autorelease];
  user = [newValue retain];
}


- (int) pid {
  return pid;
}

- (void) setPid: (int) newValue {
  pid = newValue;
}


- (double) percentCPUUsage {
  return percentCPUUsage;
}

- (void) setPercentCPUUsage: (double) newValue {
  percentCPUUsage = newValue;
}


- (double) percentResidentMemory {
  return percentResidentMemory;
}

- (void) setPercentResidentMemory: (double) newValue {
  percentResidentMemory = newValue;
}


- (int) virtualSize {
  return virtualSize;
}

- (void) setVirtualSize: (int) newValue {
  virtualSize = newValue;
}


- (int) residentMemorySize {
  return residentMemorySize;
}

- (void) setResidentMemorySize: (int) newValue {
  residentMemorySize = newValue;
}


- (XRProcessState) state {
  return state;
}

- (void) setState: (XRProcessState) newValue {
  state = newValue;
}


- (NSDate *) dateStarted {
  return dateStarted;
}

- (void) setDateStarted: (NSDate *) newValue {
  [dateStarted autorelease];
  dateStarted = [newValue retain];
}


- (NSDate *) time {
  return time;
}

- (void) setTime: (NSDate *) newValue {
  [time autorelease];
  time = [newValue retain];
}


- (NSString *) command {
  return command;
}

- (void) setCommand: (NSString *) newValue {
  [command autorelease];
  command = [newValue retain];
}

- (NSString *) path {
  return path;
}

- (void) setPath: (NSString *) newValue {
  [path autorelease];
  path = [newValue retain];
}

- (BOOL) isWindowed {
  return isWindowed;
}

- (void) setIsWindowed: (BOOL) newValue {
  isWindowed = newValue;
}

- (NSString *) description   {
    return [NSString stringWithFormat: @"XRProcess Description:\nUser: %@\nPID: %d\n%% CPU: %f\n%% Resident Memory: %f\nVirtual Size: %d\nResident Memory Size: %d\nState: %d\nStarted: %@\nTime: %@\nCommand: %@\n", 
                    [self user], 
                    [self pid], 
                    [self percentCPUUsage],
                    [self percentResidentMemory],
                    [self virtualSize],
                    [self residentMemorySize],
                    [self state], //TODO: Fix this
                    [self dateStarted],
                    [self time],
                    [self command]
                    ];
}

@end


@implementation XRProcess (Private)

+ (NSString *) readTaskDataWithArguments: (NSArray *) arguments
{
    NSTask *task = [[NSTask alloc] init];
    NSPipe *pipe = [NSPipe pipe];
    NSFileHandle *readHandle = [pipe fileHandleForReading];
    NSData *data = nil;
 
    // write handle is closed to this process
    [task setStandardOutput:pipe];
    [task setLaunchPath: @"/bin/ps"];
    [task setArguments: arguments];
    [task launch];
 
    data = [readHandle readDataToEndOfFile];
    [task release];
    
    //Convert to a string
    return [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding];
}

+ (XRProcess *) parseLine: (NSString *) line
{
    int          pid = 0;
    NSArray      *components = nil;
    XRProcess    *process = [[XRProcess alloc] init];
    
    NSArray      *launchedApplications = [[NSWorkspace sharedWorkspace ] launchedApplications];
    
    //TODO: This sucks, do this better
    line = [line stringByReplacingOccurrencesOfString:@"    " withString: @" "];
    line = [line stringByReplacingOccurrencesOfString:@"   " withString: @" "];
    line = [line stringByReplacingOccurrencesOfString:@"  " withString: @" "];
    components = [line componentsSeparatedByString: @" "];
    
    pid = [[components objectAtIndex: 1] intValue];
    
    //USER       PID %CPU %MEM    VSZ    RSS   TT  STAT STARTED      TIME     COMMAND
    //zack      1015 54.1 0.4     605724 6164  s002 R+  2:29PM       353:30.31 Xcode
    [process setUser:                   [components objectAtIndex: 0]];
    [process setPid:                    pid];
    [process setPercentCPUUsage:        [[components objectAtIndex: 2] doubleValue]];
    [process setPercentResidentMemory:  [[components objectAtIndex: 3] doubleValue]];
    [process setVirtualSize:            [[components objectAtIndex: 4] intValue]];
    [process setResidentMemorySize:     [[components objectAtIndex: 5] intValue]];
    [process setState:                  1]; //TODO
    [process setDateStarted:            [NSDate date]]; //TODO
    [process setTime:                   [NSDate date]];//TODO
    [process setPath:                   [components objectAtIndex: 10]];
    
    NSArray *array =                    [[process path] componentsSeparatedByString: @"/"];
    [process setCommand:                [array lastObject]];

    //Find launcehed app if it exists
    for (NSDictionary *launchedApplication in launchedApplications)
    {
        if ([[launchedApplication objectForKey: @"NSApplicationProcessIdentifier"] intValue ] == pid)
        {
            [process setPath: [launchedApplication objectForKey: @"NSApplicationPath"]];
            [process setCommand: [launchedApplication objectForKey: @"NSApplicationName"]];
            [process setIsWindowed: YES];
            break;
        }
    }

        
    //TODO: Need to properly parse command name i.e. "/Applications/Microsoft Office 2004" will be just "Microsoft"
    return process;
}

- (NSComparisonResult) residentMemoryCompare: (XRProcess *)compare
{
    if ([compare residentMemorySize] > [self residentMemorySize])
    {
        return NSOrderedDescending;
    }
    else if ([compare residentMemorySize] < [self residentMemorySize])
    {
        return NSOrderedAscending;
    }
    
    return NSOrderedSame;
}

- (NSComparisonResult) commandNameCompare: (XRProcess *)compare
{
    NSString *left = [self command];
    NSString *right = [compare command];
    
    return [left localizedCaseInsensitiveCompare: right];
}

- (NSComparisonResult) userCompare: (XRProcess *)compare
{
    NSString *left = [self user];
    NSString *right = [compare user];
    
    return [left localizedCaseInsensitiveCompare: right];
}


- (NSComparisonResult) vitualMemorySizeCompare: (XRProcess *) compare
{
    if ([compare virtualSize] > [self virtualSize])
    {
        return NSOrderedDescending;
    }
    else if ([compare virtualSize] < [self virtualSize])
    {
        return NSOrderedAscending;
    }
    
    return NSOrderedSame;
}

- (NSComparisonResult) percentCPUUsageCompare: (XRProcess *)compare
{
    if ([compare percentCPUUsage] > [self percentCPUUsage])
    {
        return NSOrderedDescending;
    }
    else if ([compare percentCPUUsage] < [self percentCPUUsage])
    {
        return NSOrderedAscending;
    }
    
    return NSOrderedSame;
}

@end


